package org.truenewx.tnxjee.webmvc.security.web.access;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.truenewx.tnxjee.core.Strings;
import org.truenewx.tnxjee.core.util.LogUtil;
import org.truenewx.tnxjee.model.spec.user.security.UserConfigAuthority;
import org.truenewx.tnxjee.webmvc.method.support.HandlerMethodMapping;
import org.truenewx.tnxjee.webmvc.security.config.annotation.*;
import org.truenewx.tnxjee.webmvc.util.SpringWebMvcUtil;

import java.lang.annotation.Annotation;
import java.util.*;

/**
 * WEB安全元数据源<br>
 * 用于获取访问资源需要具备的权限
 *
 * @author jianglei
 */
@Component
public class WebSecurityMetadataSource implements SecurityMetadataSource, ConfigAuthorityMethodResolver {

    @Autowired(required = false)
    private ConfigAuthorityAnnotationResolver annotationResolver;
    @Autowired
    private HandlerMethodMapping handlerMethodMapping;
    /**
     * 映射集：方法名称-所需权限清单，用户具有权限清单中的任意一个权限，即可访问对应方法
     */
    private final Map<String, Collection<UserConfigAuthority>> methodConfigAuthoritiesMapping = new HashMap<>();


    private String getDefaultPermission(String url) {
        // 确保头尾都有/
        url = StringUtils.wrapIfMissing(url, Strings.SLASH);
        // 移除可能包含的路径变量
        if (url.endsWith("/{id}/")) { // 以路径变量id结尾的，默认视为detail
            url = url.replaceAll("/\\{id\\}/", "/detail/");
        }
        url = url.replaceAll("/\\{[^}]*\\}/", Strings.SLASH);
        // 去掉头尾的/
        url = StringUtils.strip(url, Strings.SLASH);
        // 替换中间的/为_
        return url.replaceAll(Strings.SLASH, Strings.UNDERLINE);
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return HandlerMethod.class.isAssignableFrom(clazz);
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        Collection<ConfigAttribute> configAttributes = new ArrayList<>();
        this.methodConfigAuthoritiesMapping.values().forEach(configAttributes::addAll);
        return configAttributes;
    }

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        Collection<ConfigAttribute> attributes = new HashSet<>();
        if (object instanceof HandlerMethod handlerMethod) {
            Collection<UserConfigAuthority> configAttributes = resolveConfigAuthorities(handlerMethod);
            if (CollectionUtils.isNotEmpty(configAttributes)) {
                attributes.addAll(configAttributes);
            } else { // 至少加入一个没有权限限制的必备权限，以标记进行过处理
                attributes.add(new UserConfigAuthority());
            }
        }
        return attributes;
    }

    @Override
    public Collection<UserConfigAuthority> resolveConfigAuthorities(HandlerMethod handlerMethod) {
        String methodKey = handlerMethod.getMethod().toString();
        Collection<UserConfigAuthority> authorities = this.methodConfigAuthoritiesMapping.get(methodKey);
        if (authorities == null) {
            authorities = parseUserConfigAuthorities(handlerMethod);
            this.methodConfigAuthoritiesMapping.put(methodKey, authorities);
        }
        return authorities;
    }

    private Collection<UserConfigAuthority> parseUserConfigAuthorities(HandlerMethod handlerMethod) {
        Collection<UserConfigAuthority> authorities = new ArrayList<>();
        // 允许匿名访问，则忽略权限限定
        if (handlerMethod.getMethodAnnotation(ConfigAnonymous.class) != null) {
            return authorities;
        }
        String url = SpringWebMvcUtil.getRequestMappingUrl(handlerMethod);
        Annotation[] annotations = handlerMethod.getMethod().getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof ConfigAuthority configAuthority) {
                authorities.add(new UserConfigAuthority(configAuthority.type(), configAuthority.rank(),
                        configAuthority.permission(), configAuthority.intranet()));
            } else if (annotation instanceof ConfigPermission configPermission) {
                authorities.add(new UserConfigAuthority(configPermission.type(), configPermission.rank(),
                        getDefaultPermission(url), configPermission.intranet()));
            } else if (annotation instanceof ConfigAuthorities configAuthorities) {
                for (ConfigAuthority configAuthority : configAuthorities.value()) {
                    authorities.add(new UserConfigAuthority(configAuthority.type(), configAuthority.rank(),
                            configAuthority.permission(), configAuthority.intranet()));
                }
            } else if (annotation instanceof ConfigPermissions configPermissions) {
                for (ConfigPermission configPermission : configPermissions.value()) {
                    authorities.add(new UserConfigAuthority(configPermission.type(), configPermission.rank(),
                            getDefaultPermission(url), configPermission.intranet()));
                }
            } else if (this.annotationResolver != null) {
                Collection<UserConfigAuthority> customized = this.annotationResolver.resolveConfigAuthorities(
                        annotation, url);
                if (customized != null) {
                    authorities.addAll(customized);
                }
            }
        }
        if (authorities.isEmpty()) { // 没有配置权限限定，则拒绝所有访问
            authorities.add(UserConfigAuthority.ofDenyAll());
        } else {
            StringBuilder sb = new StringBuilder();
            authorities.forEach(authority -> sb.append(Strings.COMMA).append(authority.toString()));
            sb.delete(0, 1);
            LogUtil.info(getClass(), "Config authorities: {} => {}", url, sb.toString());
        }
        return authorities;
    }

    @Override
    public Collection<UserConfigAuthority> resolveConfigAuthorities(String uri, HttpMethod method) {
        HandlerMethod handlerMethod = this.handlerMethodMapping.getHandlerMethod(uri, method);
        if (handlerMethod == null) {
            LogUtil.warn(getClass(), "There is not handlerMethod for {}->{}", method.name(), uri);
            return null;
        }
        return resolveConfigAuthorities(handlerMethod);
    }

}
